//
//  DerbyNSOAddress.m
//  DerbyOSSampler
//
//  Created by glenn andreas on 1/7/14.
//  Copyright (c) 2014 glenn andreas. All rights reserved.
//

#import "DerbyNSOdeAddress.h"
#import <arpa/inet.h>

typedef union {
    struct sockaddr sa;
    struct sockaddr_in ipv4;
    struct sockaddr_in6 ipv6;
} ip_socket_address;

@interface DerbyNSOdeAddress()
@end
@implementation DerbyNSOdeAddress {
    ip_socket_address address; // NB: We rely on Objective-C's zeroing of all ivars for this to work correctly
}
- (id) initWithData: (NSData *) data
{
    NSAssert([data length] <= sizeof(ip_socket_address),@"DerbyNSOAddress data malformed (too large)");
    self = [super init];
    if (self) {
        memcpy(&address, data.bytes, data.length);
    }
    return self;
}
- (id) initWithAddress: (struct sockaddr) addr port: (int) port
{
    self = [super init];
    if (self) {
        address.sa = addr;
        if (address.sa.sa_family == AF_INET)
            address.ipv4.sin_port = htons(port);
        else
            address.ipv6.sin6_port = htons(port);
    }
    return self;
}
- (id) initWithAddress: (struct sockaddr) addr
{
    self = [super init];
    if (self) {
        address.sa = addr;
    }
    return self;
}
- (id) initWithString: (NSString *) data; // "address:port"
{
    self = [super init];
    if (self) {
        NSArray *parts = [data componentsSeparatedByString:@":"];
        int port = 0;
        if (parts.count > 1) {
            port = [[parts lastObject]intValue];
            data = [[parts subarrayWithRange: NSMakeRange(0,parts.count-1)] componentsJoinedByString:@":"];
        }
        if (inet_pton(AF_INET,data.UTF8String, &address.ipv4) != 0) {
            if (inet_pton(AF_INET6,data.UTF8String, &address.ipv6) != 0) {
                return nil;
            }
        }
        if (port != 0) {
            if (address.sa.sa_family == AF_INET)
                address.ipv4.sin_port = htons(port);
            else
                address.ipv6.sin6_port = htons(port);
        }
    }
    return self;
}
- (BOOL) isEqual:(id)object
{
    if ([object isKindOfClass:[self class]]) {
        return memcmp(&address, &((DerbyNSOdeAddress *)object)->address, sizeof(address)) == 0;
    } else {
        return NO;
    }
}
- (int) port
{
    return ntohs(address.sa.sa_family == AF_INET ? address.ipv4.sin_port : address.ipv6.sin6_port);
}
- (struct sockaddr) address
{
    return address.sa;
}
- (NSString *) stringValue
{
    char addressBuffer[INET6_ADDRSTRLEN];
    const char *addressStr = inet_ntop(
                                       address.sa.sa_family,
                                       (address.sa.sa_family == AF_INET ? (void *)&(address.ipv4.sin_addr) : (void *)&(address.ipv6.sin6_addr)),
                                       addressBuffer,
                                       sizeof(addressBuffer));

    return [NSString stringWithFormat:@"%s:%d", addressStr, self.port];
}
@end
